package service

import (
	"ddnser/DTO"
	"ddnser/common/logger"
	"ddnser/settings"
	"ddnser/utils/errcode"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"

	"go.uber.org/zap"
)

type DdnsUpdateService interface {
	UpdateHost([]net.IP) ([]DTO.UpdateRes, errcode.ErrCode)
}

type DdnsService struct {
	logger *zap.SugaredLogger
}

func NewDdnsService() *DdnsService {
	return &DdnsService{logger: logger.GetLogger()}
}

func (ddns *DdnsService) UpdateHost(ips []net.IP) (res []DTO.UpdateRes, code errcode.ErrCode) {
	if !updateOrNot(ips) {
		code = errcode.DDNS_NO_NEED_TO_UPDATE
		ddns.logger.Infof("ddns.UpdateHost: [%d]-ip not change", code)
		return
	}
	conf := settings.GetAppConf().DdnsConf
	// NoIP update
	if conf.NoIpConf.Enable {
		noip := NewNoipUpdateService()
		r, code := noip.UpdateHost(ips)
		if code != errcode.OK {
			memo.v4 = ""
			memo.v6 = ""
		}
		res = append(res, r...)
	}
	// Dynv6 update
	if conf.Dynv6Conf.Enable {
		dynv6 := NewDynv6UpdateService()
		r, code := dynv6.UpdateHost(ips)
		if code != errcode.OK {
			memo.v4 = ""
			memo.v6 = ""
		}
		res = append(res, r...)
	}
	code = errcode.OK
	ddns.logger.Infof("ddns.UpdateHost: [%d]-%s", code, errcode.GetCodeMsg(code))
	return
}

type ipMemo struct {
	v4 string
	v6 string
}

var memo = ipMemo{}

func updateOrNot(ips []net.IP) (needUpdate bool) {
	needUpdate = true
	var v4Candidate, v6Candidate string
	for _, ip := range ips {
		if ip.To4() != nil {
			if ip.String() == memo.v4 {
				needUpdate = false
				continue
			}
			v4Candidate = ip.String()
		} else {
			if ip.String() == memo.v6 {
				needUpdate = false
			}
			v6Candidate = ip.String()
		}
	}
	if needUpdate {
		memo.v4 = v4Candidate
		memo.v6 = v6Candidate
	}
	return
}

type NoipUpdateService struct {
	logger   *zap.SugaredLogger
	protocol string
	api      string
	hostname string
	user     string
	password string
}

func NewNoipUpdateService() *NoipUpdateService {
	conf := settings.GetAppConf().NoIpConf
	var protocol string = "http"
	if conf.Ssl {
		protocol = "https"
	}
	return &NoipUpdateService{
		logger:   logger.GetLogger(),
		protocol: protocol,
		api:      conf.Api,
		hostname: conf.MyHostname,
		user:     conf.User,
		password: conf.Password,
	}
}

func (noip *NoipUpdateService) UpdateHost(ips []net.IP) (res []DTO.UpdateRes, code errcode.ErrCode) {
	uri := fmt.Sprintf(
		"%s://%s?hostname=%s&myip=%s",
		noip.protocol, noip.api, noip.hostname,
		func() string {
			var temp []string
			for _, ip := range ips {
				temp = append(temp, ip.String())
			}
			return strings.Join(temp, ",")
		}(),
	)
	client := http.Client{}
	req, _ := http.NewRequest("GET", uri, nil)
	req.SetBasicAuth(noip.user, noip.password)
	resp, err := client.Do(req)
	if err != nil {
		code = errcode.DDNS_NOIP_REQUEST_ERR
		noip.logger.Errorf("noip.UpdateHost: [%d]-client.Do err: %s", code, err)
		return []DTO.UpdateRes{
			{DdnsType: "NoIP", Host: noip.hostname, Msg: errcode.GetCodeMsg(code)},
		}, code
	}
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		code = errcode.OK
	} else {
		code = errcode.FAIL
	}
	noip.logger.Infof("noip.UpdateHost: [%d]-%s", code, errcode.GetCodeMsg(code))
	body, _ := io.ReadAll(resp.Body)
	return []DTO.UpdateRes{
		{DdnsType: "NoIP", Host: noip.hostname, Msg: strings.TrimSpace(string(body))},
	}, code
}

type Dynv6UpdateService struct {
	logger   *zap.SugaredLogger
	protocol string
	api      string
	zone     string
	token    string
}

func NewDynv6UpdateService() *Dynv6UpdateService {
	conf := settings.GetAppConf().Dynv6Conf
	var protocol string = "http"
	if conf.Ssl {
		protocol = "https"
	}
	return &Dynv6UpdateService{
		logger:   logger.GetLogger(),
		protocol: protocol,
		api:      conf.Api,
		zone:     conf.Zone,
		token:    conf.Token,
	}
}

func (dynv6 *Dynv6UpdateService) UpdateHost(ips []net.IP) (res []DTO.UpdateRes, code errcode.ErrCode) {
	uri := fmt.Sprintf(
		"%s://%s?zone=%s&token=%s&%s",
		dynv6.protocol, dynv6.api, dynv6.zone, dynv6.token,
		func() string {
			var temp []string
			for _, ip := range ips {
				if ip.To4() == nil {
					temp = append(temp, "ipv6="+ip.String())
				} else {
					temp = append(temp, "ipv4="+ip.String())
				}
			}
			return strings.Join(temp, "&")
		}(),
	)
	client := http.Client{}
	req, _ := http.NewRequest("GET", uri, nil)
	resp, err := client.Do(req)
	if err != nil {
		code = errcode.DDNS_DYNV6_REQUEST_ERR
		dynv6.logger.Errorf("dynv6.UpdateHost: [%d]-client.Do err: %s", code, err)
		return []DTO.UpdateRes{
			{DdnsType: "Dynv6", Host: dynv6.zone, Msg: errcode.GetCodeMsg(code)},
		}, code
	}
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		code = errcode.OK
	} else {
		code = errcode.FAIL
	}
	dynv6.logger.Infof("dynv6.UpdateHost: [%d]-%s", code, errcode.GetCodeMsg(code))
	body, _ := io.ReadAll(resp.Body)
	return []DTO.UpdateRes{
		{DdnsType: "Dynv6", Host: dynv6.zone, Msg: strings.TrimSpace(string(body))},
	}, code
}
